/*      */ package com.zimbra.cs.datasource.imap;
/*      */ 
/*      */ import com.zimbra.common.localconfig.KnownKey;
/*      */ import com.zimbra.common.localconfig.LC;
/*      */ import com.zimbra.common.service.RemoteServiceException;
/*      */ import com.zimbra.common.service.ServiceException;
/*      */ import com.zimbra.common.util.ArrayUtil;
/*      */ import com.zimbra.common.util.Log;
/*      */ import com.zimbra.common.util.ZimbraLog;
/*      */ import com.zimbra.cs.account.DataSource;
/*      */ import com.zimbra.cs.datasource.DataSourceManager;
/*      */ import com.zimbra.cs.datasource.IOExceptionHandler;
/*      */ import com.zimbra.cs.datasource.MessageContent;
/*      */ import com.zimbra.cs.datasource.SyncErrorManager;
/*      */ import com.zimbra.cs.datasource.SyncUtil;
/*      */ import com.zimbra.cs.db.Db;
/*      */ import com.zimbra.cs.db.Db.Error;
/*      */ import com.zimbra.cs.db.DbDataSource.DataSourceItem;
/*      */ import com.zimbra.cs.mailbox.Flag;
/*      */ import com.zimbra.cs.mailbox.Folder;
/*      */ import com.zimbra.cs.mailbox.MailItem.Type;
/*      */ import com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException;
/*      */ import com.zimbra.cs.mailbox.Mailbox;
/*      */ import com.zimbra.cs.mailbox.MailboxLock;
/*      */ import com.zimbra.cs.mailbox.Message;
/*      */ import com.zimbra.cs.mailclient.CommandFailedException;
/*      */ import com.zimbra.cs.mailclient.MailException;
/*      */ import com.zimbra.cs.mailclient.imap.Body;
/*      */ import com.zimbra.cs.mailclient.imap.CopyResult;
/*      */ import com.zimbra.cs.mailclient.imap.FetchResponseHandler;
/*      */ import com.zimbra.cs.mailclient.imap.Flags;
/*      */ import com.zimbra.cs.mailclient.imap.ImapConnection;
/*      */ import com.zimbra.cs.mailclient.imap.ListData;
/*      */ import com.zimbra.cs.mailclient.imap.MailboxInfo;
/*      */ import com.zimbra.cs.mailclient.imap.MessageData;
/*      */ import com.zimbra.cs.mime.ParsedMessage;
/*      */ import com.zimbra.cs.util.Zimbra;
/*      */ import java.io.IOException;
/*      */ import java.sql.SQLException;
/*      */ import java.util.ArrayList;
/*      */ import java.util.Arrays;
/*      */ import java.util.Collection;
/*      */ import java.util.Collections;
/*      */ import java.util.Date;
/*      */ import java.util.Iterator;
/*      */ import java.util.List;
/*      */ import java.util.Map;
/*      */ import java.util.Set;
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ class ImapFolderSync
/*      */ {
/*      */   private final ImapSync imapSync;
/*      */   private final ImapConnection connection;
/*      */   private final DataSource ds;
/*      */   private final Mailbox mailbox;
/*   70 */   private final Statistics stats = new Statistics(null);
/*      */   
/*      */   private ImapFolder tracker;
/*      */   private LocalFolder localFolder;
/*      */   private RemoteFolder remoteFolder;
/*      */   private FolderSyncState syncState;
/*      */   private MailboxInfo mailboxInfo;
/*      */   private ImapMessageCollection trackedMsgs;
/*      */   private Set<Integer> localMsgIds;
/*      */   private List<Integer> newMsgIds;
/*      */   private List<Long> addedUids;
/*      */   private List<Long> deletedUids;
/*      */   private long maxUid;
/*      */   private boolean completed;
/*      */   private int totalErrors;
/*      */   private boolean fullSync;
/*      */   private boolean localDeleted;
/*   87 */   private static final Log LOG = ZimbraLog.datasource;
/*      */   
/*   89 */   private static final int FETCH_SIZE = LC.data_source_fetch_size.intValue();
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   private static final int MAX_ITEM_ERRORS = 3;
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */   private static final int MAX_TOTAL_ERRORS = 10;
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   public ImapFolderSync(ImapSync imapSync)
/*      */   {
/*  108 */     this.imapSync = imapSync;
/*  109 */     this.connection = imapSync.getConnection();
/*  110 */     this.ds = imapSync.getDataSource();
/*  111 */     this.mailbox = imapSync.getMailbox();
/*  112 */     this.fullSync = imapSync.isFullSync();
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */   public ImapFolder syncFolder(ListData ld)
/*      */     throws ServiceException, IOException
/*      */   {
/*  121 */     String path = ld.getMailbox();
/*  122 */     if ((this.ds.isSyncInboxOnly()) && (!path.equalsIgnoreCase("Inbox")))
/*  123 */       return null;
/*  124 */     this.remoteFolder = new RemoteFolder(this.connection, path);
/*  125 */     this.tracker = this.imapSync.getTrackedFolders().getByRemotePath(path);
/*  126 */     if (this.tracker != null) {
/*  127 */       checkTrackedFolder(ld);
/*      */     } else {
/*  129 */       createLocalFolder(ld);
/*      */     }
/*  131 */     if (this.tracker != null) {
/*  132 */       this.remoteFolder.info("syncing remote folder %s", new Object[] { path });
/*      */       
/*  134 */       this.localFolder.updateFlags(ld);
/*      */     }
/*  136 */     return this.tracker;
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   public void purgeLocalFolder(Folder folder)
/*      */     throws ServiceException, IOException
/*      */   {
/*  146 */     if (!this.ds.isSyncEnabled(folder)) {
/*  147 */       return;
/*      */     }
/*  149 */     this.localFolder = new LocalFolder(this.mailbox, folder);
/*  150 */     this.tracker = this.imapSync.getTrackedFolders().getByItemId(folder.getId());
/*  151 */     if (this.tracker != null) {
/*  152 */       this.remoteFolder = new RemoteFolder(this.connection, this.tracker.getRemoteId());
/*  153 */       if (!this.remoteFolder.exists()) {
/*  154 */         this.remoteFolder.info("folder was deleted", new Object[0]);
/*  155 */         if (this.ds.isSyncEnabled(folder))
/*  156 */           this.localFolder.delete();
/*  157 */         this.imapSync.deleteFolderTracker(this.tracker);
/*  158 */         this.tracker = null;
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   public ImapFolder syncFolder(Folder folder)
/*      */     throws ServiceException, IOException
/*      */   {
/*  170 */     if (!this.ds.isSyncEnabled(folder)) {
/*  171 */       return null;
/*      */     }
/*  173 */     this.localFolder = new LocalFolder(this.mailbox, folder);
/*  174 */     this.tracker = this.imapSync.getTrackedFolders().getByItemId(folder.getId());
/*  175 */     if (this.tracker != null) {
/*  176 */       this.remoteFolder = new RemoteFolder(this.connection, this.tracker.getRemoteId());
/*  177 */       if (!this.remoteFolder.exists()) {
/*  178 */         this.remoteFolder.info("folder was deleted", new Object[0]);
/*      */         
/*      */ 
/*  181 */         List<Integer> messagesAddedLocallySinceLastSync = this.tracker.getNewMessageIds();
/*  182 */         if (!messagesAddedLocallySinceLastSync.isEmpty()) {
/*  183 */           deleteAllMessagesExcept(messagesAddedLocallySinceLastSync);
/*      */           
/*      */ 
/*  186 */           this.imapSync.deleteFolderTracker(this.tracker);
/*  187 */           this.tracker = null;
/*  188 */           return createRemoteFolderAndTracker(folder);
/*      */         }
/*      */         
/*  191 */         if (this.ds.isSyncEnabled(folder))
/*  192 */           this.localFolder.delete();
/*  193 */         this.imapSync.deleteFolderTracker(this.tracker);
/*  194 */         this.tracker = null;
/*  195 */       } else if ((!this.ds.isSyncCapable(folder)) && (!this.localFolder.getPath().equals(this.tracker.getLocalPath())))
/*      */       {
/*  197 */         if (deleteRemoteFolder(this.remoteFolder, this.tracker.getItemId())) {
/*  198 */           this.imapSync.deleteFolderTracker(this.tracker);
/*  199 */           this.tracker = null;
/*      */         }
/*      */       }
/*  202 */     } else if (this.ds.isSyncEnabled(folder)) {
/*  203 */       return createRemoteFolderAndTracker(folder);
/*      */     }
/*  205 */     return this.tracker;
/*      */   }
/*      */   
/*      */   private void deleteAllMessagesExcept(Collection<Integer> messageIdsToPreserve) throws ServiceException {
/*  209 */     List<Integer> messageIds = this.mailbox.listItemIds(this.mailbox.getOperationContext(), MailItem.Type.MESSAGE, this.tracker.getItemId());
/*  210 */     messageIds.removeAll(messageIdsToPreserve);
/*  211 */     this.mailbox.delete(this.mailbox.getOperationContext(), ArrayUtil.toIntArray(messageIds), MailItem.Type.MESSAGE, null);
/*      */   }
/*      */   
/*      */   private ImapFolder createRemoteFolderAndTracker(Folder folder) throws IOException, ServiceException {
/*  215 */     this.remoteFolder = createRemoteFolder(folder);
/*  216 */     if (this.remoteFolder == null)
/*  217 */       return null;
/*      */     try {
/*  219 */       this.mailboxInfo = this.remoteFolder.status();
/*      */     } catch (CommandFailedException e) {
/*  221 */       syncFolderFailed(folder.getId(), this.remoteFolder.getPath(), "Unable to select remote folder", e);
/*      */       
/*  223 */       return null;
/*      */     }
/*  225 */     this.tracker = this.imapSync.createFolderTracker(folder.getId(), folder.getPath(), this.remoteFolder.getPath(), this.mailboxInfo.getUidValidity());
/*      */     
/*      */ 
/*      */ 
/*  229 */     this.fullSync = true;
/*  230 */     return this.tracker;
/*      */   }
/*      */   
/*      */   private RemoteFolder createRemoteFolder(Folder folder) throws ServiceException, IOException
/*      */   {
/*  235 */     String remotePath = this.imapSync.getRemotePath(folder);
/*  236 */     if (remotePath == null) {
/*  237 */       return null;
/*      */     }
/*  239 */     RemoteFolder newFolder = new RemoteFolder(this.connection, remotePath);
/*      */     try {
/*  241 */       newFolder.create();
/*      */     } catch (CommandFailedException e) {
/*  243 */       syncFolderFailed(folder.getId(), remotePath, "Unable to create remote folder", e);
/*      */       
/*  245 */       return null;
/*      */     }
/*  247 */     return newFolder;
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */   public void syncMessages()
/*      */     throws ServiceException, IOException
/*      */   {
/*  255 */     this.localFolder.debug("Syncing messages for folder", new Object[0]);
/*  256 */     if (!isSyncEnabled()) {
/*  257 */       this.localFolder.debug("Synchronization disabled for this folder", new Object[0]);
/*  258 */       this.tracker = null;
/*  259 */       return;
/*      */     }
/*      */     
/*      */ 
/*  263 */     this.syncState = this.imapSync.removeSyncState(this.localFolder.getId());
/*  264 */     if ((this.syncState == null) || (this.fullSync)) {
/*  265 */       this.syncState = newSyncState();
/*  266 */       this.fullSync = true;
/*      */     }
/*      */     
/*      */ 
/*  270 */     if (this.mailboxInfo == null) {
/*  271 */       this.mailboxInfo = ((this.fullSync) || (this.remoteFolder.isSelected()) ? this.remoteFolder.select() : this.remoteFolder.status());
/*      */     }
/*      */     
/*      */ 
/*  275 */     if (!checkUidValidity()) {
/*  276 */       this.mailboxInfo = this.remoteFolder.select();
/*  277 */       this.syncState = newSyncState();
/*  278 */       this.fullSync = true;
/*      */     }
/*      */     
/*      */ 
/*      */ 
/*  283 */     MessageChanges changes = null;
/*  284 */     if (!this.fullSync) {
/*  285 */       changes = MessageChanges.getChanges(this.ds, this.localFolder.getFolder(), this.syncState.getLastChangeId());
/*      */       
/*  287 */       if ((!changes.hasChanges()) && (this.mailboxInfo.getUidNext() == this.syncState.getLastUidNext())) {
/*  288 */         this.syncState.setLastChangeId(changes.getLastChangeId());
/*  289 */         this.imapSync.putSyncState(this.localFolder.getId(), this.syncState);
/*  290 */         return;
/*      */       }
/*      */     }
/*      */     
/*  294 */     if (!this.remoteFolder.isSelected()) {
/*  295 */       this.mailboxInfo = this.remoteFolder.select();
/*      */     }
/*      */     
/*  298 */     long uidNext = this.mailboxInfo.getUidNext();
/*  299 */     this.syncState.setLastUidNext(uidNext);
/*  300 */     long lastFetchedUid = this.syncState.getLastFetchedUid();
/*  301 */     if ((uidNext > 0L) && (uidNext <= lastFetchedUid)) {
/*  302 */       String msg = String.format("Inconsistent UIDNEXT value from server (got %d but last fetched uid %d)", new Object[] { Long.valueOf(uidNext), Long.valueOf(lastFetchedUid) });
/*      */       
/*      */ 
/*  305 */       if (isYahoo())
/*      */       {
/*      */ 
/*      */ 
/*      */ 
/*  310 */         throw RemoteServiceException.YMAIL_INCONSISTENT_STATE();
/*      */       }
/*  312 */       ServiceException e = ServiceException.FAILURE(msg, null);
/*  313 */       syncFolderFailed(this.tracker.getItemId(), this.tracker.getLocalPath(), msg, e);
/*  314 */       throw e;
/*      */     }
/*      */     
/*      */ 
/*  318 */     this.newMsgIds = new ArrayList();
/*  319 */     this.addedUids = new ArrayList();
/*  320 */     this.deletedUids = new ArrayList();
/*  321 */     if (this.fullSync)
/*      */     {
/*      */ 
/*  324 */       if ((hasCopyUid()) && (!this.ds.isImportOnly())) {
/*  325 */         moveMessages();
/*      */       }
/*  327 */       syncFlags(lastFetchedUid);
/*  328 */     } else if (changes != null) {
/*  329 */       int lastModSeq = this.syncState.getLastChangeId();
/*  330 */       if (lastModSeq > 0)
/*      */       {
/*  332 */         pushChanges(changes);
/*  333 */         this.syncState.setLastChangeId(changes.getLastChangeId());
/*      */       }
/*      */     }
/*      */     
/*      */ 
/*  338 */     IOExceptionHandler.getInstance().resetSyncCounter(this.mailbox);
/*  339 */     this.maxUid = (uidNext > 0L ? uidNext - 1L : 0L);
/*  340 */     if ((this.mailboxInfo.getExists() > 0L) && ((this.maxUid <= 0L) || (lastFetchedUid < this.maxUid))) {
/*  341 */       List<Long> uids = this.remoteFolder.getUids(lastFetchedUid + 1L, this.maxUid);
/*  342 */       if (uids.size() > 0) {
/*  343 */         fetchMessages(uids);
/*      */       }
/*      */     }
/*  346 */     if (!this.addedUids.isEmpty()) {
/*  347 */       Collections.sort(this.addedUids, Collections.reverseOrder());
/*  348 */       fetchMessages(this.addedUids);
/*      */     }
/*  350 */     IOExceptionHandler.getInstance().checkpointIOExceptionRate(this.mailbox);
/*      */     
/*      */     Iterator i$;
/*  353 */     if (!this.ds.isImportOnly()) {
/*  354 */       for (i$ = this.deletedUids.iterator(); i$.hasNext();) { long uid = ((Long)i$.next()).longValue();
/*  355 */         deleteMessage(uid);
/*      */       }
/*      */     }
/*  358 */     this.remoteFolder.close();
/*      */     
/*      */ 
/*  361 */     this.trackedMsgs = null;
/*  362 */     this.localMsgIds = null;
/*  363 */     this.completed = true;
/*      */   }
/*      */   
/*      */   private boolean isSyncEnabled() throws ServiceException {
/*  367 */     return (this.tracker != null) && (this.tracker.getUidValidity() > 0L) && (this.ds.isSyncEnabled(this.localFolder.getFolder()));
/*      */   }
/*      */   
/*      */   private FolderSyncState newSyncState() throws ServiceException
/*      */   {
/*  372 */     FolderSyncState ss = new FolderSyncState();
/*  373 */     this.mailbox.lock.lock();
/*      */     try {
/*  375 */       this.trackedMsgs = this.tracker.getMessages();
/*  376 */       this.localMsgIds = this.localFolder.getMessageIds();
/*  377 */       ss.setLastChangeId(this.mailbox.getLastChangeID());
/*      */     } finally {
/*  379 */       this.mailbox.lock.release();
/*      */     }
/*  381 */     ss.setLastFetchedUid(this.trackedMsgs.getLastUid());
/*  382 */     return ss;
/*      */   }
/*      */   
/*      */   private void syncFlags(long lastUid) throws ServiceException, IOException
/*      */   {
/*  387 */     if (lastUid > 0L) {
/*  388 */       fetchFlags(lastUid);
/*      */     }
/*      */     
/*  391 */     for (Iterator i$ = this.localMsgIds.iterator(); i$.hasNext();) { int id = ((Integer)i$.next()).intValue();
/*  392 */       ImapMessage trackedMsg = this.trackedMsgs.getByItemId(id);
/*  393 */       if (trackedMsg != null) {
/*  394 */         this.localFolder.deleteMessage(id);
/*  395 */         trackedMsg.delete();
/*  396 */         this.stats.msgsDeletedLocally += 1;
/*      */       } else {
/*  398 */         this.newMsgIds.add(Integer.valueOf(id));
/*  399 */         this.stats.msgsAddedRemotely += 1;
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   private void pushChanges(MessageChanges changes)
/*      */     throws ServiceException, IOException
/*      */   {
/*  416 */     this.localFolder.debug("Pushing changes: %s", new Object[] { changes });
/*  417 */     for (MessageChange change : changes.getChanges()) {
/*  418 */       clearError(change.getItemId());
/*  419 */       if (change.isAdded()) {
/*  420 */         this.newMsgIds.add(Integer.valueOf(change.getItemId()));
/*  421 */       } else if (change.isDeleted()) {
/*  422 */         deleteMessage(change.getTracker().getUid());
/*  423 */       } else if (change.isUpdated()) {
/*  424 */         int flags = change.getTracker().getFlags();
/*  425 */         updateFlags(change.getTracker(), SyncUtil.zimbraToImapFlags(flags));
/*  426 */       } else if ((change.isMoved()) && (change.getMessage().getFolderId() != this.localFolder.getId()))
/*      */       {
/*      */ 
/*  429 */         if (!moveMessage(change.getTracker())) {
/*  430 */           deleteMessage(change.getTracker().getUid());
/*      */         }
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   public void finishSync() throws ServiceException {
/*  437 */     if (!this.completed) return;
/*  438 */     if ((this.newMsgIds != null) && (!this.newMsgIds.isEmpty()) && (!this.ds.isImportOnly())) {
/*  439 */       appendMsgs(this.newMsgIds);
/*      */     }
/*  441 */     if (this.syncState != null) {
/*  442 */       this.imapSync.putSyncState(this.localFolder.getId(), this.syncState);
/*      */     }
/*  444 */     if (LOG.isDebugEnabled()) {
/*  445 */       if (this.stats.flagsUpdatedLocally > 0) {
/*  446 */         this.localFolder.debug("Updated %d flags", new Object[] { Integer.valueOf(this.stats.flagsUpdatedLocally) });
/*      */       }
/*  448 */       if (this.stats.flagsUpdatedRemotely > 0) {
/*  449 */         this.remoteFolder.debug("Updated %d flags", new Object[] { Integer.valueOf(this.stats.flagsUpdatedRemotely) });
/*      */       }
/*  451 */       if (this.stats.msgsAddedLocally > 0) {
/*  452 */         this.localFolder.debug("Added %d new messages", new Object[] { Integer.valueOf(this.stats.msgsAddedLocally) });
/*      */       }
/*  454 */       if (this.stats.msgsAddedRemotely > 0) {
/*  455 */         this.remoteFolder.debug("Added %d new messages", new Object[] { Integer.valueOf(this.stats.msgsAddedRemotely) });
/*      */       }
/*  457 */       if (this.stats.msgsDeletedLocally > 0) {
/*  458 */         this.localFolder.debug("Deleted %d messages", new Object[] { Integer.valueOf(this.stats.msgsDeletedLocally) });
/*      */       }
/*  460 */       if (this.stats.msgsDeletedRemotely > 0) {
/*  461 */         this.remoteFolder.debug("Deleted %d messages", new Object[] { Integer.valueOf(this.stats.msgsDeletedRemotely) });
/*      */       }
/*  463 */       if (this.stats.msgsCopiedRemotely > 0) {
/*  464 */         this.remoteFolder.debug("Copied %d messages", new Object[] { Integer.valueOf(this.stats.msgsCopiedRemotely) });
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   public LocalFolder getLocalFolder()
/*      */   {
/*  471 */     return this.localFolder;
/*      */   }
/*      */   
/*      */   private void checkTrackedFolder(ListData ld) throws ServiceException, IOException
/*      */   {
/*  476 */     this.localFolder = LocalFolder.fromId(this.mailbox, this.tracker.getItemId());
/*  477 */     if ((this.localFolder == null) || ((!this.ds.isSyncCapable(this.localFolder.getFolder())) && (!this.localFolder.getPath().equals(this.tracker.getLocalPath()))))
/*      */     {
/*  479 */       LOG.debug("Local folder '%s' was deleted", new Object[] { this.tracker.getLocalPath() });
/*  480 */       if (deleteRemoteFolder(this.remoteFolder, this.tracker.getItemId())) {
/*  481 */         this.imapSync.deleteFolderTracker(this.tracker);
/*  482 */         this.tracker = null;
/*  483 */       } else if (this.localDeleted) {
/*  484 */         LOG.error("Unable to delete remote folder, creating local folder with sync off");
/*  485 */         createLocalFolder(ld);
/*  486 */         SyncUtil.setSyncEnabled(this.mailbox, this.localFolder.getId(), false);
/*  487 */         this.localDeleted = false;
/*      */       } else {
/*  489 */         this.tracker = null;
/*      */       }
/*  491 */     } else if (!this.localFolder.getPath().equals(this.tracker.getLocalPath()))
/*      */     {
/*  493 */       renameFolder(ld, this.localFolder.getId());
/*      */     }
/*      */   }
/*      */   
/*      */   private boolean deleteRemoteFolder(RemoteFolder folder, int itemId) throws ServiceException, IOException
/*      */   {
/*      */     try {
/*  500 */       folder.delete();
/*      */     } catch (CommandFailedException e) {
/*  502 */       syncFolderFailed(itemId, folder.getPath(), "Unable to delete remote folder", e);
/*  503 */       return false;
/*      */     }
/*  505 */     return true;
/*      */   }
/*      */   
/*      */   private boolean renameFolder(ListData ld, int itemId) throws ServiceException, IOException
/*      */   {
/*  510 */     String localPath = this.localFolder.getPath();
/*  511 */     String newRemotePath = this.imapSync.getRemotePath(this.localFolder.getFolder());
/*  512 */     this.localFolder.info("folder was renamed (originally '%s')", new Object[] { this.tracker.getLocalPath() });
/*  513 */     if (newRemotePath != null)
/*      */     {
/*      */       try {
/*  516 */         if (!newRemotePath.equals(this.remoteFolder.getPath()))
/*  517 */           this.remoteFolder = this.remoteFolder.renameTo(newRemotePath);
/*      */       } catch (CommandFailedException e) {
/*  519 */         syncFolderFailed(itemId, localPath, "Unable to rename remote folder to " + newRemotePath, e);
/*      */         
/*  521 */         return false;
/*      */       }
/*  523 */       this.tracker.setLocalPath(localPath);
/*  524 */       this.tracker.setRemoteId(newRemotePath);
/*  525 */       this.tracker.update();
/*  526 */       this.imapSync.removeSyncState(this.tracker.getFolderId());
/*      */     }
/*      */     else
/*      */     {
/*  530 */       this.localFolder.info("folder was moved outside data source root", new Object[0]);
/*  531 */       this.imapSync.deleteFolderTracker(this.tracker);
/*      */       
/*  533 */       createLocalFolder(ld);
/*  534 */       return this.tracker != null;
/*      */     }
/*  536 */     return true;
/*      */   }
/*      */   
/*      */   private void createLocalFolder(ListData ld) throws ServiceException {
/*  540 */     String remotePath = ld.getMailbox();
/*  541 */     String localPath = this.imapSync.getLocalPath(ld);
/*  542 */     if (localPath == null)
/*      */     {
/*  544 */       this.tracker = null;
/*  545 */       return;
/*      */     }
/*      */     
/*      */ 
/*      */     try
/*      */     {
/*  551 */       this.mailboxInfo = this.remoteFolder.status();
/*      */     } catch (IOException e) {
/*  553 */       this.remoteFolder.info("Error in STATUS command", e);
/*  554 */       this.tracker = null;
/*  555 */       return;
/*      */     }
/*  557 */     long uidValidity = this.mailboxInfo.getUidValidity();
/*      */     
/*  559 */     this.localFolder = new LocalFolder(this.mailbox, localPath);
/*  560 */     if (!this.localFolder.exists()) {
/*  561 */       this.localFolder.create();
/*      */     }
/*      */     
/*  564 */     localPath = this.localFolder.getPath();
/*  565 */     this.tracker = this.imapSync.createFolderTracker(this.localFolder.getId(), localPath, remotePath, uidValidity);
/*      */   }
/*      */   
/*      */   private void appendMsgs(List<Integer> itemIds) throws ServiceException
/*      */   {
/*  570 */     this.remoteFolder.info("Appending %d message(s) to remote IMAP folder", new Object[] { Integer.valueOf(itemIds.size()) });
/*  571 */     ImapAppender appender = newImapAppender(this.remoteFolder.getPath());
/*  572 */     for (Iterator i$ = itemIds.iterator(); i$.hasNext();) { int id = ((Integer)i$.next()).intValue();
/*  573 */       if (skipItem(id)) {
/*  574 */         LOG.warn("Skipping append of item %d due to previous errors", new Object[] { Integer.valueOf(id) });
/*      */       }
/*      */       else {
/*  577 */         Message msg = this.localFolder.getMessage(id);
/*  578 */         if (msg == null) {
/*  579 */           clearError(id);
/*      */         }
/*      */         else {
/*  582 */           this.remoteFolder.debug("Appending new message with item id %d", new Object[] { Integer.valueOf(id) });
/*      */           
/*  584 */           ImapMessage msgTracker = getMsgTracker(id);
/*  585 */           if (msgTracker != null) {
/*  586 */             msgTracker.delete();
/*      */           }
/*      */           try {
/*  589 */             long uid = appender.appendMessage(msg);
/*      */             try {
/*  591 */               msgTracker = this.tracker.getMessage(uid);
/*  592 */               if (msgTracker.getItemId() != id)
/*      */               {
/*      */ 
/*  595 */                 this.localFolder.deleteMessage(id);
/*      */               }
/*      */             } catch (MailServiceException.NoSuchItemException e) {
/*  598 */               storeImapMessage(uid, id, msg.getFlagBitmask());
/*      */             }
/*      */           } catch (Exception e) {
/*  601 */             syncMessageFailed(id, "Append message failed", e);
/*      */           }
/*      */         }
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*  608 */   private ImapMessage getMsgTracker(int itemId) throws ServiceException { try { return this.tracker.getMessage(itemId);
/*      */     } catch (MailServiceException.NoSuchItemException e) {}
/*  610 */     return null;
/*      */   }
/*      */   
/*      */   private void storeImapMessage(long uid, int msgId, int flags)
/*      */     throws ServiceException
/*      */   {
/*  616 */     ImapMessage msgTracker = new ImapMessage(this.ds, this.localFolder.getId(), msgId, flags, uid);
/*  617 */     msgTracker.add();
/*  618 */     this.syncState.updateLastFetchedUid(uid);
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */   private boolean checkUidValidity()
/*      */     throws ServiceException
/*      */   {
/*  626 */     if (this.mailboxInfo.getUidValidity() == this.tracker.getUidValidity()) {
/*  627 */       return true;
/*      */     }
/*  629 */     this.remoteFolder.info("Resynchronizing folder because UIDVALIDITY has changed from %d to %d", new Object[] { Long.valueOf(this.tracker.getUidValidity()), Long.valueOf(this.mailboxInfo.getUidValidity()) });
/*      */     
/*      */ 
/*  632 */     List<Integer> newLocalIds = this.tracker.getNewMessageIds();
/*  633 */     ImapAppender appender; Iterator i$; if (newLocalIds.size() > 0) {
/*  634 */       this.remoteFolder.info("Copying %d messages to remote folder", new Object[] { Integer.valueOf(newLocalIds.size()) });
/*  635 */       appender = newImapAppender(this.remoteFolder.getPath());
/*  636 */       for (i$ = newLocalIds.iterator(); i$.hasNext();) { int id = ((Integer)i$.next()).intValue();
/*  637 */         clearError(id);
/*  638 */         Message msg = this.localFolder.getMessage(id);
/*  639 */         if (msg != null) {
/*      */           try {
/*  641 */             appender.appendMessage(msg);
/*  642 */             this.localFolder.deleteMessage(id);
/*      */           } catch (Exception e) {
/*  644 */             syncMessageFailed(id, "Append message failed", e);
/*      */           }
/*      */         }
/*      */       }
/*      */     }
/*      */     
/*  650 */     this.mailbox.emptyFolder(null, this.tracker.getItemId(), false);
/*  651 */     this.localFolder.emptyFolder();
/*  652 */     this.tracker.deleteMappings();
/*  653 */     this.tracker.setUidValidity(Long.valueOf(this.mailboxInfo.getUidValidity()));
/*  654 */     this.tracker.update();
/*  655 */     return false;
/*      */   }
/*      */   
/*      */   private ImapAppender newImapAppender(String path) {
/*  659 */     return new ImapAppender(this.connection, path).setHasAppendUid(hasAppendUid());
/*      */   }
/*      */   
/*      */   private void fetchFlags(long lastUid) throws ServiceException, IOException {
/*  663 */     String seq = "1:" + lastUid;
/*  664 */     this.remoteFolder.debug("Fetching flags for UID sequence %s", new Object[] { seq });
/*  665 */     fetchFlags(seq);
/*      */   }
/*      */   
/*      */   private void fetchFlags(String seq) throws ServiceException, IOException {
/*  669 */     Map<Long, MessageData> mds = this.connection.uidFetch(seq, "FLAGS");
/*      */     
/*  671 */     removeDeleted(mds);
/*  672 */     for (MessageData md : mds.values()) {
/*  673 */       long uid = md.getUid();
/*  674 */       ImapMessage trackedMsg = this.trackedMsgs.getByUid(uid);
/*  675 */       if (trackedMsg != null) {
/*  676 */         int msgId = trackedMsg.getItemId();
/*  677 */         if (this.localMsgIds.contains(Integer.valueOf(msgId))) {
/*  678 */           this.localMsgIds.remove(Integer.valueOf(msgId));
/*      */           try {
/*  680 */             updateFlags(trackedMsg, md.getFlags());
/*  681 */             clearError(msgId);
/*      */           }
/*      */           catch (MailServiceException.NoSuchItemException e) {
/*  684 */             this.deletedUids.add(Long.valueOf(uid));
/*      */             
/*  686 */             clearError(msgId);
/*      */           } catch (Exception e) {
/*  688 */             syncMessageFailed(msgId, "Unable to update message flags", e);
/*      */           }
/*      */         } else {
/*  691 */           this.deletedUids.add(Long.valueOf(uid));
/*  692 */           clearError(msgId);
/*      */         }
/*      */       } else {
/*  695 */         this.remoteFolder.debug("Adding new message with UID %d detected while syncing flags", new Object[] { Long.valueOf(uid) });
/*      */         
/*  697 */         this.addedUids.add(Long.valueOf(uid));
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   private void updateFlags(ImapMessage msg, Flags flags)
/*      */     throws ServiceException, IOException
/*      */   {
/*  705 */     int id = msg.getItemId();
/*  706 */     int localFlags = msg.getItemFlags();
/*  707 */     int trackedFlags = msg.getFlags();
/*  708 */     int remoteFlags = SyncUtil.imapToZimbraFlags(flags);
/*  709 */     int newLocalFlags = mergeFlags(localFlags, trackedFlags, remoteFlags);
/*  710 */     int newRemoteFlags = SyncUtil.imapFlagsOnly(newLocalFlags);
/*  711 */     if ((LOG.isDebugEnabled()) && ((newLocalFlags != localFlags) || (newRemoteFlags != remoteFlags) || (newRemoteFlags != trackedFlags)))
/*      */     {
/*  713 */       this.localFolder.debug("Updating flags for message with item id %d: local=%s, tracked=%s, remote=%s, new_local=%s, new_remote=%s", new Object[] { Integer.valueOf(id), Flag.toString(localFlags), Flag.toString(trackedFlags), Flag.toString(remoteFlags), Flag.toString(newLocalFlags), Flag.toString(newRemoteFlags) });
/*      */     }
/*      */     
/*      */ 
/*      */ 
/*  718 */     if (newLocalFlags != localFlags) {
/*  719 */       this.localFolder.setMessageFlags(id, newLocalFlags);
/*  720 */       this.stats.flagsUpdatedLocally += 1;
/*      */     }
/*  722 */     if (newRemoteFlags != remoteFlags) {
/*  723 */       String uids = String.valueOf(msg.getUid());
/*  724 */       Flags toAdd = SyncUtil.getFlagsToAdd(flags, newRemoteFlags);
/*  725 */       Flags toRemove = SyncUtil.getFlagsToRemove(flags, newRemoteFlags);
/*      */       
/*  727 */       if (!toAdd.isEmpty()) {
/*  728 */         this.connection.uidStore(uids, "+FLAGS.SILENT", toAdd);
/*      */       }
/*  730 */       if (!toRemove.isEmpty()) {
/*  731 */         this.connection.uidStore(uids, "-FLAGS.SILENT", toRemove);
/*      */       }
/*  733 */       this.stats.flagsUpdatedRemotely += 1;
/*      */     }
/*  735 */     if (newRemoteFlags != trackedFlags) {
/*  736 */       msg.setFlags(newRemoteFlags);
/*  737 */       msg.update();
/*  738 */       this.stats.flagsUpdatedLocally += 1;
/*      */     }
/*      */   }
/*      */   
/*      */   private void fetchMessages(List<Long> uids) throws ServiceException, IOException {
/*  743 */     this.remoteFolder.debug("Fetching %d new IMAP message(s)", new Object[] { Integer.valueOf(uids.size()) });
/*  744 */     long lastCheckTime = System.currentTimeMillis();
/*  745 */     ImapFolderSync inbox = (!this.localFolder.isInbox()) && (this.ds.isOffline()) ? this.imapSync.getInboxFolderSync() : null;
/*      */     
/*  747 */     removeSkippedUids(uids);
/*  748 */     Iterator<Long> it = uids.iterator();
/*  749 */     while (it.hasNext()) {
/*  750 */       this.imapSync.checkIsEnabled();
/*  751 */       fetchMessages(nextFetchSeq(it));
/*      */       
/*  753 */       this.ds.checkPendingMessages();
/*  754 */       long time = System.currentTimeMillis();
/*  755 */       long freq = this.ds.getSyncFrequency();
/*  756 */       if ((this.maxUid > 0L) && (freq > 0L) && (time - lastCheckTime > freq)) {
/*  757 */         lastCheckTime = time;
/*  758 */         if ((inbox != null) && (inbox.hasNewRemoteMessages()))
/*      */         {
/*      */ 
/*  761 */           this.remoteFolder.debug("Found new INBOX messages during sync", new Object[0]);
/*  762 */           inbox.fetchNewMessages();
/*  763 */           this.mailboxInfo = this.remoteFolder.select();
/*      */         }
/*      */         
/*  766 */         fetchNewMessages();
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   private void fetchNewMessages() throws IOException, ServiceException {
/*  772 */     if (!this.remoteFolder.isSelected()) {
/*  773 */       this.mailboxInfo = this.remoteFolder.select();
/*      */     }
/*  775 */     List<Long> newUids = this.remoteFolder.getUids(this.maxUid + 1L, 0L);
/*  776 */     if (!newUids.isEmpty()) {
/*  777 */       this.remoteFolder.debug("Fetching %d newly arrived IMAP message(s)", new Object[] { Integer.valueOf(newUids.size()) });
/*  778 */       Iterator<Long> it = newUids.iterator();
/*      */       do {
/*  780 */         fetchMessages(nextFetchSeq(it));
/*  781 */       } while (it.hasNext());
/*  782 */       this.maxUid = ((Long)newUids.get(0)).longValue();
/*      */     }
/*  784 */     this.syncState.setLastUidNext(this.mailboxInfo.getUidNext());
/*      */   }
/*      */   
/*      */   private boolean hasNewRemoteMessages() throws IOException {
/*  788 */     if ((this.syncState != null) && (this.tracker != null)) {
/*  789 */       MailboxInfo mi = this.remoteFolder.status();
/*  790 */       return (mi.getUidValidity() == this.tracker.getUidValidity()) && (mi.getUidNext() != this.syncState.getLastUidNext());
/*      */     }
/*      */     
/*  793 */     return false;
/*      */   }
/*      */   
/*      */   private void removeSkippedUids(List<Long> uids)
/*      */   {
/*  798 */     Iterator<Long> it = uids.iterator();
/*  799 */     while (it.hasNext()) {
/*  800 */       long uid = ((Long)it.next()).longValue();
/*  801 */       if (skipUid(uid)) {
/*  802 */         LOG.warn("Skipping fetch of uid %d due to previous errors", new Object[] { Long.valueOf(uid) });
/*  803 */         it.remove();
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   private String nextFetchSeq(Iterator<Long> uids) {
/*  809 */     StringBuilder sb = new StringBuilder();
/*  810 */     sb.append(uids.next());
/*  811 */     for (int count = FETCH_SIZE; uids.hasNext();) { count--; if (count <= 0) break;
/*  812 */       sb.append(',').append(uids.next());
/*      */     }
/*  814 */     return sb.toString();
/*      */   }
/*      */   
/*      */   private void fetchMessages(String seq) throws ServiceException, IOException {
/*  818 */     final Map<Long, MessageData> flagsByUid = this.connection.uidFetch(seq, "(FLAGS INTERNALDATE)");
/*      */     
/*  820 */     removeDeleted(flagsByUid);
/*  821 */     final Set<Long> uidSet = flagsByUid.keySet();
/*  822 */     if (uidSet.isEmpty()) return;
/*  823 */     FetchResponseHandler handler = new FetchResponseHandler()
/*      */     {
/*      */       public void handleFetchResponse(MessageData md) throws Exception {
/*  826 */         long uid = md.getUid();
/*  827 */         IOExceptionHandler.getInstance().trackSyncItem(ImapFolderSync.this.mailbox, uid);
/*      */         try {
/*  829 */           ImapFolderSync.this.handleFetch(md, flagsByUid);
/*  830 */           ImapFolderSync.this.clearError(uid);
/*      */         } catch (OutOfMemoryError e) {
/*  832 */           Zimbra.halt("Out of memory", e);
/*      */         } catch (Exception e) {
/*  834 */           if (!IOExceptionHandler.getInstance().isRecoverable(ImapFolderSync.this.mailbox, uid, "Exception syncing UID " + uid + " in folder " + ImapFolderSync.this.remoteFolder.getPath(), e)) {
/*  835 */             ImapFolderSync.this.syncFailed("Fetch failed for uid " + uid, e);
/*  836 */             SyncErrorManager.incrementErrorCount(ImapFolderSync.this.ds, ImapFolderSync.this.remoteId(uid));
/*      */           }
/*      */         }
/*  839 */         uidSet.remove(Long.valueOf(uid));
/*      */       }
/*      */       
/*  842 */     };
/*  843 */     LOG.debug("Fetching messages for sequence: " + seq);
/*      */     try {
/*  845 */       this.connection.uidFetch(getSequence(uidSet), "BODY.PEEK[]", handler);
/*      */     } catch (CommandFailedException e) {
/*  847 */       String msg = "UID FETCH failed: " + e.toString();
/*  848 */       checkCanContinue(msg, e);
/*  849 */       LOG.warn(msg, e);
/*      */     }
/*  851 */     if (uidSet.isEmpty()) return;
/*  852 */     LOG.info("Fetching remaining messages one at a time for UIDs: " + uidSet);
/*  853 */     for (Iterator i$ = getOrderedUids(uidSet).iterator(); i$.hasNext();) { long uid = ((Long)i$.next()).longValue();
/*      */       try {
/*  855 */         LOG.info("Fetching message for uid: " + uid);
/*  856 */         MessageData md = this.connection.uidFetch(uid, "BODY.PEEK[]");
/*  857 */         if (md == null)
/*      */         {
/*  859 */           throw ServiceException.FAILURE("Server returned no response for UID FETCH " + uid + " BODY.PEEK[]", null);
/*      */         }
/*  861 */         handler.handleFetchResponse(md);
/*      */       } catch (Exception e) {
/*  863 */         String msg = "Error while fetching message for UID " + uid;
/*  864 */         checkCanContinue(msg, e);
/*  865 */         LOG.warn(msg, e);
/*      */       }
/*      */     }
/*  868 */     if (!uidSet.isEmpty()) {
/*  869 */       LOG.error("Unable to fetch messages for uids: " + uidSet);
/*      */     }
/*      */   }
/*      */   
/*      */   private void removeDeleted(Map<Long, MessageData> mds)
/*      */   {
/*  875 */     Iterator<MessageData> it = mds.values().iterator();
/*  876 */     while (it.hasNext()) {
/*  877 */       MessageData md = (MessageData)it.next();
/*  878 */       Flags flags = md.getFlags();
/*  879 */       if ((flags != null) && (flags.isDeleted())) {
/*  880 */         this.remoteFolder.debug("Remote message with uid %d is flagged \\Deleted", new Object[] { Long.valueOf(md.getUid()) });
/*      */         
/*  882 */         it.remove();
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   private static String getSequence(Set<Long> uidSet) {
/*  888 */     StringBuilder sb = new StringBuilder();
/*  889 */     Iterator<Long> it = getOrderedUids(uidSet).iterator();
/*  890 */     sb.append(it.next());
/*  891 */     while (it.hasNext()) {
/*  892 */       sb.append(',').append(it.next());
/*      */     }
/*  894 */     return sb.toString();
/*      */   }
/*      */   
/*      */   private static Collection<Long> getOrderedUids(Collection<Long> uidSet) {
/*  898 */     List<Long> uids = new ArrayList(uidSet);
/*  899 */     Collections.sort(uids, Collections.reverseOrder());
/*  900 */     return uids;
/*      */   }
/*      */   
/*      */   private void handleFetch(MessageData md, Map<Long, MessageData> flagsByUid) throws ServiceException, IOException
/*      */   {
/*  905 */     long uid = md.getUid();
/*  906 */     if (uid == -1L) {
/*  907 */       throw new MailException("Missing UID in FETCH response");
/*      */     }
/*  909 */     MessageData flagsData = (MessageData)flagsByUid.get(Long.valueOf(uid));
/*  910 */     this.remoteFolder.debug("Found new IMAP message with uid %d", new Object[] { Long.valueOf(uid) });
/*      */     
/*  912 */     Date date = flagsData.getInternalDate();
/*  913 */     Long receivedDate = date != null ? Long.valueOf(date.getTime()) : null;
/*  914 */     int zflags = SyncUtil.imapToZimbraFlags(flagsData.getFlags());
/*  915 */     int folderId = this.localFolder.getId();
/*  916 */     MessageContent mc = getContent(md);
/*      */     Message msg;
/*      */     try {
/*  919 */       ParsedMessage pm = mc.getParsedMessage(receivedDate, this.mailbox.attachmentsIndexingEnabled());
/*  920 */       if (pm == null) {
/*  921 */         this.remoteFolder.warn("Empty message body for UID %d. Must be ignored.", new Object[] { Long.valueOf(uid) }); return;
/*      */       }
/*      */       
/*  924 */       msg = this.imapSync.addMessage(null, pm, mc.getSize(), folderId, zflags, mc.getDeliveryContext());
/*      */     } finally {
/*  926 */       mc.cleanup();
/*      */     }
/*  928 */     if ((msg != null) && (msg.getFolderId() == folderId)) {
/*  929 */       storeImapMessage(uid, msg.getId(), zflags);
/*  930 */       this.stats.msgsAddedLocally += 1;
/*      */ 
/*      */ 
/*      */     }
/*      */     else
/*      */     {
/*      */ 
/*      */ 
/*  938 */       this.deletedUids.add(Long.valueOf(uid));
/*      */     }
/*      */   }
/*      */   
/*      */   private static MessageContent getContent(MessageData md) throws MailException {
/*  943 */     Body[] sections = md.getBodySections();
/*  944 */     if ((sections == null) || (sections.length != 1)) {
/*  945 */       throw new MailException("Invalid body section FETCH response for uid " + md.getUid());
/*      */     }
/*      */     
/*  948 */     return (MessageContent)sections[0].getData();
/*      */   }
/*      */   
/*      */   private boolean deleteMessage(long uid) throws ServiceException, IOException {
/*  952 */     if (skipUid(uid)) {
/*  953 */       LOG.warn("Skipping remote delete of uid %d due to previous errors", new Object[] { Long.valueOf(uid) });
/*  954 */       return false;
/*      */     }
/*      */     try {
/*  957 */       this.remoteFolder.deleteMessage(uid);
/*  958 */       this.stats.msgsDeletedRemotely += 1;
/*  959 */       clearError(uid);
/*      */     } catch (CommandFailedException e) {
/*  961 */       syncMessageFailed(uid, "Cannot delete message with uid " + uid, e);
/*  962 */       return false;
/*      */     }
/*      */     try {
/*  965 */       this.tracker.getMessage(uid).delete();
/*      */     }
/*      */     catch (MailServiceException.NoSuchItemException e) {}
/*      */     
/*  969 */     return true;
/*      */   }
/*      */   
/*      */   private void moveMessages() throws IOException, ServiceException {
/*  973 */     Collection<DbDataSource.DataSourceItem> mappings = this.tracker.getMappings();
/*  974 */     List<Integer> allIds = this.mailbox.listItemIds(this.mailbox.getOperationContext(), MailItem.Type.MESSAGE, this.tracker.getItemId());
/*      */     
/*  976 */     Integer[] sortedIds = (Integer[])allIds.toArray(new Integer[allIds.size()]);
/*      */     
/*  978 */     Arrays.sort(sortedIds);
/*  979 */     for (DbDataSource.DataSourceItem mapping : mappings) {
/*  980 */       if (Arrays.binarySearch(sortedIds, Integer.valueOf(mapping.itemId)) < 0) {
/*  981 */         moveMessage(new ImapMessage(this.ds, mapping));
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */   private boolean moveMessage(ImapMessage msgTracker)
/*      */     throws ServiceException, IOException
/*      */   {
/*  989 */     if (!hasCopyUid())
/*  990 */       return false;
/*      */     Message msg;
/*  992 */     Folder folder; try { msg = this.mailbox.getMessageById(null, msgTracker.getItemId());
/*  993 */       folder = this.mailbox.getFolderById(null, msg.getFolderId());
/*      */     } catch (MailServiceException.NoSuchItemException e) {
/*  995 */       return false;
/*      */     }
/*  997 */     if (!this.ds.isSyncEnabled(folder))
/*  998 */       return false;
/*  999 */     int fid = folder.getId();
/* 1000 */     ImapFolderCollection trackedFolders = this.imapSync.getTrackedFolders();
/* 1001 */     ImapFolder folderTracker = trackedFolders.getByItemId(fid);
/*      */     String remotePath;
/* 1003 */     String remotePath; if (folderTracker != null) {
/* 1004 */       remotePath = folderTracker.getRemoteId();
/*      */     }
/*      */     else {
/* 1007 */       RemoteFolder newFolder = createRemoteFolder(folder);
/* 1008 */       if (newFolder == null) return false;
/* 1009 */       remotePath = newFolder.getPath();
/*      */     }
/*      */     CopyResult cr;
/*      */     try {
/* 1013 */       cr = this.remoteFolder.copyMessage(msgTracker.getUid(), remotePath);
/*      */     } catch (IOException e) {
/* 1015 */       syncMessageFailed(msgTracker.getUid(), "COPY failed", e);
/* 1016 */       return false;
/*      */     }
/* 1018 */     if (cr == null) {
/* 1019 */       return false;
/*      */     }
/* 1021 */     this.stats.msgsCopiedRemotely += 1;
/*      */     
/* 1023 */     if (folderTracker == null) {
/* 1024 */       long uv = cr.getUidValidity();
/* 1025 */       if (uv == 0L) uv = 1L;
/* 1026 */       this.imapSync.createFolderTracker(fid, folder.getPath(), remotePath, uv);
/*      */     }
/*      */     else
/*      */     {
/* 1030 */       ImapFolderSync syncedFolder = this.imapSync.getSyncedFolder(fid);
/* 1031 */       if ((syncedFolder != null) && (syncedFolder.newMsgIds != null)) {
/* 1032 */         syncedFolder.newMsgIds.remove(Integer.valueOf(msg.getId()));
/*      */       }
/*      */     }
/* 1035 */     if (!deleteMessage(msgTracker.getUid())) {
/* 1036 */       LOG.warn("Unable to delete message with uid " + msgTracker.getUid());
/* 1037 */       return false;
/*      */     }
/*      */     
/* 1040 */     msgTracker.delete();
/* 1041 */     long uid = cr.getToUids()[0];
/* 1042 */     msgTracker = new ImapMessage(this.ds, fid, msg.getId(), msgTracker.getFlags(), uid);
/* 1043 */     msgTracker.add();
/*      */     
/*      */ 
/* 1046 */     ImapFolderSync syncedFolder = this.imapSync.getSyncedFolder(fid);
/* 1047 */     if ((syncedFolder != null) && (syncedFolder.syncState != null)) {
/* 1048 */       syncedFolder.syncState.updateLastFetchedUid(uid);
/*      */     } else {
/* 1050 */       FolderSyncState fss = this.imapSync.getFolderSyncState(fid);
/* 1051 */       if (fss != null) {
/* 1052 */         fss.updateLastFetchedUid(uid);
/*      */       }
/*      */     }
/* 1055 */     return true;
/*      */   }
/*      */   
/*      */ 
/*      */ 
/*      */ 
/*      */ 
/*      */   private static int mergeFlags(int localFlags, int trackedFlags, int remoteFlags)
/*      */   {
/* 1064 */     return trackedFlags & localFlags & remoteFlags | (trackedFlags ^ 0xFFFFFFFF) & (localFlags | remoteFlags);
/*      */   }
/*      */   
/*      */   private boolean hasCopyUid()
/*      */   {
/* 1069 */     return (this.connection.hasUidPlus()) || (isYahoo());
/*      */   }
/*      */   
/*      */   private boolean hasAppendUid() {
/* 1073 */     return (this.connection.hasUidPlus()) || (isYahoo());
/*      */   }
/*      */   
/*      */   private boolean isYahoo() {
/* 1077 */     return ImapUtil.isYahoo(this.connection);
/*      */   }
/*      */   
/*      */   private boolean skipItem(int itemId) {
/* 1081 */     return SyncErrorManager.getErrorCount(this.ds, itemId) >= 3;
/*      */   }
/*      */   
/*      */   private boolean skipUid(long uid) {
/* 1085 */     return SyncErrorManager.getErrorCount(this.ds, remoteId(uid)) >= 3;
/*      */   }
/*      */   
/*      */   private String remoteId(long uid) {
/* 1089 */     return this.mailboxInfo.getUidValidity() + ":" + uid;
/*      */   }
/*      */   
/*      */   private void clearError(int itemId) {
/* 1093 */     SyncErrorManager.clearError(this.ds, itemId);
/*      */   }
/*      */   
/*      */   private void clearError(long uid) {
/* 1097 */     SyncErrorManager.clearError(this.ds, remoteId(uid));
/*      */   }
/*      */   
/*      */   private void syncFailed(String msg, Exception e) throws ServiceException
/*      */   {
/* 1102 */     checkCanContinue(msg, e);
/* 1103 */     LOG.error(msg, e);
/* 1104 */     this.ds.reportError(-1, msg, e);
/*      */   }
/*      */   
/*      */   private void syncFolderFailed(int itemId, String path, String msg, Exception e) throws ServiceException
/*      */   {
/* 1109 */     checkCanContinue(msg, e);
/* 1110 */     int count = SyncErrorManager.incrementErrorCount(this.ds, itemId);
/* 1111 */     if (count <= 3) {
/* 1112 */       LOG.error(msg, e);
/* 1113 */       if (count == 3) {
/* 1114 */         String error = String.format("Synchronization of folder '%s' disabled due to error: %s", new Object[] { path, msg });
/*      */         
/*      */ 
/* 1117 */         this.ds.reportError(itemId, error, e);
/*      */         try {
/* 1119 */           if (this.ds.isOffline())
/*      */           {
/* 1121 */             SyncUtil.setSyncEnabled(DataSourceManager.getInstance().getMailbox(this.ds), itemId, false);
/*      */           }
/*      */         }
/*      */         catch (MailServiceException.NoSuchItemException ex) {
/* 1125 */           this.localDeleted = true;
/*      */         }
/*      */         
/* 1128 */         clearError(itemId);
/*      */       }
/*      */     }
/*      */   }
/*      */   
/*      */ 
/*      */   private void syncMessageFailed(int itemId, String msg, Exception e)
/*      */     throws ServiceException
/*      */   {
/* 1137 */     if (!(e instanceof CommandFailedException)) {
/* 1138 */       checkCanContinue(msg, e);
/*      */     }
/* 1140 */     int count = SyncErrorManager.incrementErrorCount(this.ds, itemId);
/* 1141 */     if (count <= 3) {
/* 1142 */       LOG.error(msg, e);
/* 1143 */       if (count == 3) {
/* 1144 */         this.ds.reportError(itemId, msg, e);
/*      */       }
/*      */     }
/* 1147 */     incrementTotalErrors();
/* 1148 */     if (((e instanceof CommandFailedException)) && (!((CommandFailedException)e).canContinue()))
/*      */     {
/* 1150 */       throw ServiceException.FAILURE(msg, e);
/*      */     }
/*      */   }
/*      */   
/*      */   private void syncMessageFailed(long uid, String msg, Exception e) throws ServiceException
/*      */   {
/* 1156 */     checkCanContinue(msg, e);
/* 1157 */     int count = SyncErrorManager.incrementErrorCount(this.ds, remoteId(uid));
/* 1158 */     if (count <= 3) {
/* 1159 */       LOG.error(msg, e);
/* 1160 */       if (count == 3) {
/* 1161 */         this.ds.reportError(-1, msg, e);
/*      */       }
/*      */     }
/* 1164 */     incrementTotalErrors();
/*      */   }
/*      */   
/*      */   private void incrementTotalErrors() throws ServiceException {
/* 1168 */     this.totalErrors += 1;
/* 1169 */     if (this.totalErrors > 10) {
/* 1170 */       String error = String.format("Synchronization of folder '%s' disabled due to maximum number of per-item errors exceeded", new Object[] { this.localFolder.getPath() });
/*      */       
/*      */ 
/* 1173 */       this.ds.reportError(this.localFolder.getId(), error, ServiceException.FAILURE(error, null));
/*      */       try
/*      */       {
/* 1176 */         SyncUtil.setSyncEnabled(DataSourceManager.getInstance().getMailbox(this.ds), this.localFolder.getId(), false);
/*      */       }
/*      */       catch (MailServiceException.NoSuchItemException e) {}
/*      */     }
/*      */   }
/*      */   
/*      */   private void checkCanContinue(String msg, Exception e)
/*      */     throws ServiceException
/*      */   {
/* 1185 */     if (!canContinue(e)) {
/* 1186 */       LOG.error(msg, e);
/* 1187 */       if ((e instanceof ServiceException)) {
/* 1188 */         throw ((ServiceException)e);
/*      */       }
/* 1190 */       throw ServiceException.FAILURE(msg, e);
/*      */     }
/*      */   }
/*      */   
/*      */   private boolean canContinue(Throwable e)
/*      */   {
/* 1196 */     if (!this.ds.isOffline())
/* 1197 */       return false;
/* 1198 */     if ((e instanceof ServiceException)) {
/* 1199 */       Throwable cause = e.getCause();
/* 1200 */       return (cause == null) || (canContinue(cause)); }
/* 1201 */     if ((e instanceof SQLException))
/* 1202 */       return Db.errorMatches((SQLException)e, Db.Error.DUPLICATE_ROW);
/* 1203 */     if ((e instanceof CommandFailedException)) {
/* 1204 */       return ((CommandFailedException)e).canContinue();
/*      */     }
/* 1206 */     return false;
/*      */   }
/*      */   
/*      */   private static class Statistics
/*      */   {
/*      */     int flagsUpdatedLocally;
/*      */     int flagsUpdatedRemotely;
/*      */     int msgsAddedLocally;
/*      */     int msgsAddedRemotely;
/*      */     int msgsDeletedLocally;
/*      */     int msgsDeletedRemotely;
/*      */     int msgsCopiedRemotely;
/*      */   }
/*      */ }


/* Location:              /home/mint/zimbrastore.jar!/com/zimbra/cs/datasource/imap/ImapFolderSync.class
 * Java compiler version: 7 (51.0)
 * JD-Core Version:       0.7.1
 */